////////////////////////////////////////////////////////////////////////////////
// the following routines are the text plane primatives. this plane sits
// behind the graphics plane. the following commands are provided:
// putch (x, y, character)              - puts a single character at (x,y)
// gotoxy (x, y)                        - moves cursor to (x,y)
// clear (x1, y1, x2, y2)               - clears a rectangular area
// scroll (x1, y1, x2, y2, direction)   - direction = -1 (down) or +1 (up)
// linescroll (x1, x2, y1, direction)   - direction = -1 (left) or +1 (right)
// emit (ch)                            - print (ch) at cursor, update cursor
//
// note: emit scrolls within Tmargin and Bmargin,
// as well as respecting the setting of VTinsMode
////////////////////////////////////////////////////////////////////////////////

procedure putch(X, Y:integer; ch:widechar);    // needs to be widechar to keep Lazarus happy
var FGmask, BGmask:byte;
begin
  case DimOpt of 0:begin
                     if DimText then FGmask:=$0
                                else FGmask:=$8;
                     BGmask:=$0
                   end;
                 1:begin
                     FGmask:=$8;
                     BGmask:=$0
                   end
              else begin
                     FGmask:=$8;
                     if BGcolour<>0 then BGmask:=$8
                                    else BGmask:=$0
                   end;
  end;  { of case }

  if InvText then begin
                    SCR.Font.Color:=PAL[BGcolour or BGmask];
                    SCR.Brush.Color:=PAL[FGColour or FGmask]
                  end
             else begin
                    SCR.Font.Color:=PAL[FGcolour or FGmask];
                    SCR.Brush.Color:=PAL[BGColour or BGmask]
                  end;
  SCR.Font.Style:=TxtStyle;

  if not SKIPPRINT then
  if CSR.AutoSize then SCR.TextOut(CSR.Width*(X-1), CSR.Height*(Y-1), ch)           // "Terminal", monospaced
                     else begin                                                     // manual font override
                            SCR.FillRect(Rect(CSR.Width*(X-1), CSR.Height*(Y-1),
                                              CSR.Width*(X),   CSR.Height*(Y)  ));
                            SCR.TextOut(CSR.Width*(X-1)
                                      +(CSR.Width-SCR.TextWidth(ch)) div 2,
                                        CSR.Height*(Y-1), ch)
                          end;

  if (X in [1..COLS]) and (Y in [1..ROWS]) then TextStore[Y, X]:=ch
end;


procedure gotoxy(X, Y:integer);        // -1 indicate no change
begin
  if X<>-1 then
  begin
    Xpos:=X;                           // Xpos = 1 .. ROWS
    if Xpos<1 then Xpos:=1;
    if Xpos>COLS then Xpos:=COLS
  end;

  if Y<>-1 then
  begin
    Ypos:=Y;                           // Ypos = 1 .. COLS
    if Ypos<1 then Ypos:=1;
    if Ypos>ROWS then Ypos:=ROWS
  end;

  CSR.Tag:=1;                                  // flag cursor position as moved
  if CursorVis and (PB.idx=0) then CSR.Show    // ensure cursor is visible after moving
end;


procedure clear(X1, Y1, X2, Y2:integer);
var X, Y:integer;
begin
  if X2<X1 then begin X:=X1; X1:=X2; X2:=X end;
  if Y2<Y1 then begin Y:=Y1; Y1:=Y2; Y2:=Y end;
  X1:=min(COLS, max(X1, 1));                                   // limit range to valid screen
  X2:=min(COLS, max(X2, 1));
  Y1:=min(ROWS, max(Y1, 1));
  Y2:=min(ROWS, max(Y2, 1));

  SCR.Brush.Color:=PAL[BGColour];
  SCR.FillRect(Rect(CSR.Width*(X1-1), CSR.Height*(Y1-1),
                    CSR.Width*(X2),   CSR.Height*(Y2)  ));

  for Y:=Y1 to Y2 do FillChar(TextStore[Y, X1], X2-X1+1, ' ')
end;


procedure scroll(X1, Y1, X2, Y2, direction:integer);   // +1 = scroll up, -1 = scroll down
var R1, R2:TRect;
      X, Y:integer;
begin
  if X2<X1 then begin X:=X1; X1:=X2; X2:=X end;
  if Y2<Y1 then begin Y:=Y1; Y1:=Y2; Y2:=Y end;
  X1:=min(COLS, max(X1, 1));                                   // limit range to valid screen
  X2:=min(COLS, max(X2, 1));
  Y1:=min(ROWS, max(Y1, 1));
  Y2:=min(ROWS, max(Y2, 1));

  R1:=Rect(CSR.Width*(X1-1), CSR.Height*(Y1-1),                // upper rectangle
           CSR.Width*(X2  ), CSR.Height*(Y2-1));
  R2:=Rect(CSR.Width*(X1-1), CSR.Height*(Y1  ),                // lower rectangle
           CSR.Width*(X2  ), CSR.Height*(Y2  ));

  SCR.Brush.Color:=PAL[BGColour];

  case direction of +1:begin                           // scroll screen upwards by 1 line
                         SCR.CopyRect(R1, SCR, R2);    // destination=R1, canvas, source=R2
                         R2.Top:=R1.Bottom;
               //        inc(R2.Bottom);
               //        inc(R2.Right);
                         SCR.FillRect(R2);
                         for Y:=Y1 to Y2-1 do move(TextStore[Y+1, X1], TextStore[Y, X1], X2-X1+1);
                                              //   source,             destination,      count
                         FillChar(TextStore[Y2, X1], X2-X1+1, ' ')     // blank line at bottom
                       end;
                    -1:begin;                          // scroll screen downwards by 1 line
                         SCR.CopyRect(R2, SCR, R1);    // destination=R2, canvas, source=R1
                         R1.Bottom:=R2.Top;    // -1;
               //        inc(R1.Right);
                         SCR.FillRect(R1);
                         for Y:=Y2 downto Y1+1 do move(TextStore[Y-1, X1], TextStore[Y, X1], X2-X1+1);
                                                  //   source,             destination,      count
                         FillChar(TextStore[Y1, X1], X2-X1+1, ' ')     // blank line at top
                       end
                 else ShowMessage(#13+pL+'invalid screen scroll value (+1,-1 required)'+pR+#13)
  end  { of case }
end;


procedure linescroll(X1, X2, Y1, direction:integer);   // -1 = scroll left, +1 = scroll right
var R1, R2:TRect;
         X:integer;
begin
  if X2<X1 then begin X:=X1; X1:=X2; X2:=X end;
  X1:=min(COLS, max(X1, 1));                                   // limit range to valid screen
  X2:=min(COLS, max(X2, 1));
  Y1:=min(ROWS, max(Y1, 1));

  R1:=Rect(CSR.Width*(X1-1), CSR.Height*(Y1-1),                // left rectangle
           CSR.Width*(X2-1), CSR.Height*(Y1  ));
  R2:=Rect(CSR.Width*(X1  ), CSR.Height*(Y1-1),                // right rectangle
           CSR.Width*(X2  ), CSR.Height*(Y1  ));

  SCR.Brush.Color:=PAL[BGColour];

  case direction of -1:begin                           // scroll line left
                         SCR.CopyRect(R1, SCR, R2);    // destination=R1, canvas, source=R2
                         R2.Left:=R1.Right;
                         SCR.FillRect(R2);

                         for X:=X1 to X2-1 do TextStore[Y1,X]:=TextStore[Y1,X+1];
                         TextStore[Y1,X2]:=' '         // blank RHS character
                       end;

                    +1:begin;                          // scroll line right
                         SCR.CopyRect(R2, SCR, R1);    // destination=R2, canvas, source=R1
                         R1.Right:=R2.Left;
                         SCR.FillRect(R1);

                         for X:=X2 downto X1+1 do TextStore[Y1,X]:=TextStore[Y1,X-1];
                         TextStore[Y1,X1]:=' '         // blank LHS character
                       end
                 else ShowMessage(#13+pL+'invalid line scroll value (+1,-1 required)'+pR+#13)
  end  { of case }
end;


procedure emit(ch:char);
begin
  lastC:=ch;
  if ch<#32 then case ch of #07:;       //      windows.beep(440,100);               // bell    *** beep not supported in LAZARUS
                            #08:if Xpos<>1 then dec(Xpos);           // backspace
                            #09:begin
                                  if Xpos>COLS then
                                  begin
                                    Xpos:=1;
                                    inc(Ypos)
                                  end;
                                  repeat                             // tab
                                    putch(Xpos, Ypos, ' ');
                                    inc(Xpos)
                                  until (Xpos-1) mod 8=0
                                end;
                            #10:begin                                // linefeed
                                  inc(Ypos);
                                  if Ypos=Bmargin+1 then
                                  begin
                                    scroll(1, Tmargin, COLS, Bmargin, +1);
                                    Ypos:=Bmargin
                                  end;
                                  if Ypos>ROWS then Ypos:=ROWS
                                end;
                            #13:Xpos:=1;                             // carriage return
                            #17:;                                    // DC 1
                            #18:;                                    // DC 2
                            #19:;                                    // DC 3
                            #20:;                                    // DC 4
                            #27:;                                    // escape
                 end  { of case }
            else
                 if ch<#127 then                                                   // exclude characters 127 to 255 to keep linux happy   :-)
                 begin
                   if Xpos>COLS then
                   begin
//                   windows.beep(880,100);                    // line overrun
                     Xpos:=1;
                     inc(Ypos);
                     if Ypos=Bmargin+1 then
                     begin
                       scroll(1, Tmargin, COLS, Bmargin, +1);
                       Ypos:=Bmargin
                     end;
                     if Ypos>ROWS then Ypos:=ROWS
                   end;

                   if VTinsMode and (Xpos<COLS) then linescroll(Xpos, COLS, Ypos, +1);
                   putch(Xpos, Ypos, ch);
                   inc(Xpos)
                 end;

  if ch in [#8,#9,#10,#13,#32..#127 {, #128..#255}] then                           // exclude characters 127 to 255 to keep linux happy   :-)
  begin
    CSR.Tag:=1;                                    // flag cursor position as moved
    if CursorVis and (PB.idx=0) then CSR.Show;     // ensure cursor is visible after moving

    if LOGTOFILE then
    try
      Write(LogFile, ch);                          // write to log file
      if ch=#13 then Flush(LogFile)
    except
      try CloseFile(LogFile) except end;
      LOGTOFILE:=false
    end
  end
end;


////////////////////////////////////////////////////////////////////////////////
// the following routines are the graphic plane primatives. this plane sits
// in front of the text plane. the following commands are provided:
// Gw                              - returns width of graphic area
// Gh                              - returns height of graphic area
// GFXclear ((x1, y1, x2, y2)      - erase a rectular area
// GFXlineAB (x1, y1, x2, y2)      - draw a line from (x1,y1) to (x2,y2)
// GFXarc (x1, y1, x2, y2, A1, A2) - draw arc within a rectangle, from
//                                   A1 to A2 degrees; 0 = 12 o'clock
// GFXplot (x, y)                  - plot a single pixel
// GFXink (R, G, B, width)         - set ink colour and pen width
// GFXfill (x, y)                  - fill an area we have just enclosed
// GFXmoveto (x, y)                - set starting location
// DFXdrawto (x, y)                - draw from previous location to (x,y)
// GFXscroll(x1, y1, x2, y2,
//           deltaX, deltaY)       - scroll a graphics area
////////////////////////////////////////////////////////////////////////////////

function Gw:integer;
begin
  result:=Form1.Image2.Picture.Bitmap.Width
end;


function Gh:integer;
begin
  result:=Form1.Image2.Picture.Bitmap.Height
end;


procedure GFXclear(X1, Y1, X2, Y2:integer);
begin
  if X1<X2 then inc(X2)                // +1 as fillrect normally excludes RHS edge
           else inc(X1);
  if Y1<Y2 then inc(Y2)                // +1 as fillrect normally excludes bottom edge
           else inc(Y1);
  GFX.Brush.Color:=clBlack;
  GFX.FillRect(Rect(X1, Y1, X2, Y2))
end;


procedure GFXlineAB(X1, Y1, X2, Y2:integer);
begin
  GFX.MoveTo(X1, Y1);
  GFX.LineTo(X2, Y2)                   // draw line, excluding the last point
//GFX.LineTo(X2, Y2)                   // fill in last point ????????????????
end;


procedure GFXarc(X1, Y1, X2, Y2:integer; A1, A2:single);
var X0, Y0, X3, Y3, X4, Y4:integer;
begin
  A1:=(A1*pi/180.0)-(pi/2.0);            // convert A1 to radians, shift origin ccw 1/4 turn
  A2:=(A2*pi/180.0)-(pi/2.0);            // convert A2 to radians, shift origin ccw 1/4 turn

  X0:=(X1 + X2) div 2;                   // locate centre of elipse: X0
  Y0:=(Y1 + Y2) div 2;                   // locate centre of elipse: Y0

  X3:=X0 + trunc(1000.0*Cos(A2));
  Y3:=Y0 + trunc(1000.0*Sin(A2));
  X4:=X0 + trunc(1000.0*Cos(A1));
  Y4:=Y0 + trunc(1000.0*Sin(A1));

  GFX.Arc(X1, Y1, X2, Y2, X3, Y3, X4, Y4)
end;


procedure GFXplot(X, Y:integer);
begin
  GFX.Pixels[X, Y]:=GFX.Pen.Color
end;


procedure GFXink(R, G, B, width:integer);
begin
  R:=min(255, max(R, 0));
  G:=min(255, max(G, 0));
  B:=min(255, max(B, 0));
  GFX.Pen.Color:=(B*$10000) + (G*$100) + R;
  GFX.Pen.Width:=width
end;


procedure GFXfill(X, Y:integer);
begin
  GFX.Brush.Color:=GFX.Pen.Color;                  // select pen colour as fill
  GFX.FloodFill(X, Y, GFX.Pen.Color, fsBorder);    // fill everything enclosed by pen colour
  GFX.Brush.Color:=clBlack                         // go back to a black brush
(*
  GFX.Brush.Color:=clBlack;                        // select black fill colour
  GFX.FloodFill(X, Y, GFX.Pen.Color, fsBorder);    // fill area bounded by pen colour
  GFX.Brush.Color:=GFX.Pen.Color;                  // select pen colour as fill
  GFX.FloodFill(X, Y, clBlack, fsSurface);         // fill everything we previously set to black
  GFX.Brush.Color:=clBlack                         // go back to a black brush
*)
end;


procedure GFXmoveto(X, Y:integer);
begin
  GFX.MoveTo(X, Y)                                 // set starting point of a multi-line
end;


procedure GFXdrawto(X, Y:integer);
begin
  GFX.LineTo(X, Y)                                 // draw line to (x,y)
end;                                               // excluding the last point

(*
procedure GFXscroll_OLD(X1, Y1, X2, Y2, deltaX, deltaY:integer);
var R1, R2, Fh, Fv:TRect;
begin
  if (deltaX=0) and (deltaY=0) then exit;      // nothing to do!

// R1 is the source rectangle, R2 is the destingation rectangle
  if deltaX<0 then begin                       // moving *** left ***
                     R1.Left:=X1-deltaX;       // - source left side
                     R1.Right:=X2;             // - source right side
                     R2.Left:=X1;              // - destination left side
                     R2.Right:=X2+deltaX;      // - destination right side

                     Fv.Left:=R2.Right+1;
                     Fv.Right:=X2+1            // +1 as fillrect skips right side
                   end
              else begin                       // moving *** right ***
                     R1.Left:=X1;              // - source left side
                     R1.Right:=X2-deltaX;      // - source right side
                     R2.Left:=X1+deltaX;       // - destination left side
                     R2.Right:=X2;             // - destination right side

                     Fv.Left:=X1;
                     Fv.Right:=R2.Left         // fillrect skips right side
                   end;
  Fv.Top:=Y1;
  Fv.Bottom:=Y2+1;                             // +1 as fillrect skips bottom edge

  if deltaY<0 then begin                       // moving *** up ***
                     R1.Top:=Y1-deltaY;        // - source top side
                     R1.Bottom:=Y2;            // - source bottom side
                     R2.Top:=Y1;               // - destination top side
                     R2.Bottom:=Y2+deltaY;     // - destination bottom side

                     Fh.Top:=R2.Bottom+1;
                     Fh.Bottom:=Y2+1           // +1 as fillrect skips bottom edge
                   end
              else begin                       // moving *** down  ***
                     R1.Top:=Y1;               // - source top side
                     R1.Bottom:=Y2-deltaY;     // - source bottom side
                     R2.Top:=Y1+deltaY;        // - destination top side
                     R2.Bottom:=Y2;            // - destination bottom side

                     Fh.Top:=Y1;
                     Fh.Bottom:=R2.Top         // fillrect skips bottom edge
                   end;
  Fh.Left:=X1;
  Fh.Right:=Y2+1;                              // +1 as fillrect skips right side

  GFX.CopyRect(R2, GFX, R1);    // destination=R2, canvas, source=R1

  GFX.Brush.Color:=clBlack;
  if deltaX<>0 then GFX.FillRect(Fv);
  if deltaY<>0 then GFX.FillRect(Fh)
end;
*)

////////////////////////////////////////////////////////////////////////////////
// assumes CopyRect and FillRect both skip right column and bottom row        //
////////////////////////////////////////////////////////////////////////////////

procedure GFXscroll(X1, Y1, X2, Y2, deltaX, deltaY:integer);
var R1, R2, Fh, Fv:TRect;
begin
  if (deltaX=0) and (deltaY=0) then exit;      // nothing to do!

  if X1<X2 then inc(X2)                // +1 as copy/fillrect normally excludes RHS edge
           else inc(X1);
  if Y1<Y2 then inc(Y2)                // +1 as copy/fillrect normally excludes bottom edge
           else inc(Y1);

// R1 is the source rectangle, R2 is the destingation rectangle
  if deltaX<0 then begin                       // moving *** left ***
                     R1.Left:=X1-deltaX;       // - source left side
                     R1.Right:=X2;             // - source right side
                     R2.Left:=X1;              // - destination left side
                     R2.Right:=X2+deltaX;      // - destination right side

                     Fv.Left:=R2.Right;
                     Fv.Right:=X2              // set up vertical strip to clear
                   end
              else begin                       // moving *** right ***
                     R1.Left:=X1;              // - source left side
                     R1.Right:=X2-deltaX;      // - source right side
                     R2.Left:=X1+deltaX;       // - destination left side
                     R2.Right:=X2;             // - destination right side

                     Fv.Left:=X1;
                     Fv.Right:=R2.Left         // set up vertical strip to clear
                   end;
  Fv.Top:=Y1;
  Fv.Bottom:=Y2;                               // set up vertical strip to clear

  if deltaY<0 then begin                       // moving *** up ***
                     R1.Top:=Y1-deltaY;        // - source top side
                     R1.Bottom:=Y2;            // - source bottom side
                     R2.Top:=Y1;               // - destination top side
                     R2.Bottom:=Y2+deltaY;     // - destination bottom side

                     Fh.Top:=R2.Bottom;        // set up horizontal strip to clear
                     Fh.Bottom:=Y2
                   end
              else begin                       // moving *** down  ***
                     R1.Top:=Y1;               // - source top side
                     R1.Bottom:=Y2-deltaY;     // - source bottom side
                     R2.Top:=Y1+deltaY;        // - destination top side
                     R2.Bottom:=Y2;            // - destination bottom side

                     Fh.Top:=Y1;               // set up horizontal strip to clear
                     Fh.Bottom:=R2.Top
                   end;
  Fh.Left:=X1;
  Fh.Right:=Y2;                                // set up horizontal strip to clear

  GFX.CopyRect(R2, GFX, R1);    // destination=R2, canvas, source=R1

  GFX.Brush.Color:=clBlack;
  if deltaX<>0 then GFX.FillRect(Fv);          // if needsbe blank vertical strip
  if deltaY<>0 then GFX.FillRect(Fh)           // if needsbe blank horizontal strip
end;
